package com.sec.android.app.myfiles.operation;

import android.app.Activity;
import android.app.FragmentManager;
import android.content.Context;
import android.content.res.Resources;

import com.sec.android.app.myfiles.MyFilesTestRunner;
import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.info.AppConstants;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.local.file.LocalFileRecord;
import com.sec.android.app.myfiles.operation.compress.CompressOptions;
import com.sec.android.app.myfiles.shadow.custom.ShadowMyFilesLog;
import com.sec.android.app.myfiles.shadow.custom.ShadowSamsungAnalyticsLog;
import com.sec.android.app.myfiles.testFeature;
import com.sec.android.app.myfiles.util.SemFwWrapper;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.reflect.Whitebox;
import org.powermock.reflect.internal.WhiteboxImpl;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowLooper;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Created by jaebae on 2016-06-08.
 */
@RunWith(MyFilesTestRunner.class)
public class testFileOperator {
    Context mContext;
    FileOperator mFileOperator;
    FileRecord mDstFile;
    ArrayList<FileRecord> mSrcFileList;

    @Before
    public void setUp() throws Exception {
        Robolectric.getBackgroundThreadScheduler().pause();
        Robolectric.getForegroundThreadScheduler().pause();

        Activity mActivity = Robolectric.buildActivity(Activity.class).create().get();
        mContext = RuntimeEnvironment.application;
        FragmentManager fragmentManager = mActivity.getFragmentManager();

        mFileOperator = FileOperator.getInstance(0, mContext, fragmentManager, null);
        mFileOperator.setOperationArgs(FileOperator.Operation.NONE, null);

        if (mSrcFileList == null) {
            String fileList[] = {
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src_dir/file1.txt",
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src_dir/file2.txt",
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src_dir/dir" //Under 8 files 1 folder user 8files
            };

            mSrcFileList = new ArrayList<>();
            for (String file : fileList) {
                mSrcFileList.add(new LocalFileRecord(file));
            }
        }
        if (mDstFile == null) {
            mDstFile = new LocalFileRecord(AppConstants.StoragePath.INTERNAL_ROOT + "/dstDir");
        }
    }

    @After
    public void tearDown() throws Exception {
        ShadowMyFilesLog.setEnableVerifyLog(false);
        ShadowSamsungAnalyticsLog.setEnableVerifyLog(false);
    }

    @Test
    public void testInit() {
        FileOperator.init();
        Assert.assertTrue(mFileOperator.isOperationCancelled());
        Assert.assertFalse(mFileOperator.isWorking());
        Assert.assertEquals(FileOperator.Operation.NONE, mFileOperator.getOperation());
    }

    @Test
    public void testDestroy() {
        mFileOperator.setOperationArgs(FileOperator.Operation.COPY, null);
        boolean isCancel = mFileOperator.isOperationCancelled();
        if (isCancel) {
            Assert.assertTrue(isCancel);
            Assert.assertEquals(FileOperator.Operation.NONE, mFileOperator.getOperation());
        } else {
            Assert.assertFalse(isCancel);
            Assert.assertEquals(FileOperator.Operation.COPY, mFileOperator.getOperation());
            //FileOperator.destroy(0);
        }
        FileOperator.destroy(-1);
    }

    @Test
    public void testSetOperationArgs() {
        FileOperationArgs args = new FileOperationArgs();
        mFileOperator.setOperationArgs(FileOperator.Operation.COMPRESS, args);
        Assert.assertEquals(FileOperator.Operation.COMPRESS, mFileOperator.getOperation());
        Assert.assertEquals(args, mFileOperator.getArgs());
    }

    @Test
    public void testGetTotalFileCount() {
        int actual = mFileOperator.getTotalFileCount(mSrcFileList);
        Assert.assertEquals("getTotalFileCount fail", 3 + 8 + 1 + 8, actual);
    }


    @Test
    public void testGetDestinationRecord() throws Exception {
        Method method = FileOperator.class.getDeclaredMethod("getDestinationRecord", FileRecord.class, FileRecord.class);
        method.setAccessible(true);
        FileRecord actual = (FileRecord) method.invoke(mFileOperator, mSrcFileList.get(0), mDstFile);

        Assert.assertNotNull(actual);
        Assert.assertEquals(mSrcFileList.get(0).getName(), actual.getName());
        Assert.assertEquals(mDstFile.getFullPath(), actual.getPath());
    }


    @Test
    public void testExecute() {
        ShadowMyFilesLog.setEnableVerifyLog(true);
        ShadowSamsungAnalyticsLog.setEnableVerifyLog(true);

        FileOperationArgs args = new FileOperationArgs();
        mFileOperator.setOperationArgs(FileOperator.Operation.COPY, args);
        mFileOperator.execute(); //will do onPreExecute
        Assert.assertNotNull(mFileOperator);

        ShadowApplication.runBackgroundTasks(); //will do doInBackground

        Assert.assertNotNull(mFileOperator);
        ShadowLooper.runUiThreadTasks(); //will do onPostExecute
        ShadowMyFilesLog.verifyLog("ret = false");
    }

    @Test
    public void testCreateDirOperation() throws Exception {
        testCreateDirOperation("newDir", "ret = true");
        testCreateDirOperation("notD_i_r", "ret = false");
    }


    public void testCreateDirOperation(String dirName, String expectedResultLog) {
        ShadowMyFilesLog.setEnableVerifyLog(true);
        ShadowSamsungAnalyticsLog.setEnableVerifyLog(true);

        FileOperationArgs args = new FileOperationArgs();
        args.mCreateFolderName = dirName;

        args.mDst = mDstFile;


        mFileOperator.setOperationArgs(FileOperator.Operation.CREATE_DIR, args);
        mFileOperator.execute(); //will do onPreExecute
        Assert.assertNotNull(mFileOperator);

        ShadowApplication.runBackgroundTasks(); //will do doInBackground

        Assert.assertNotNull(mFileOperator);
        ShadowLooper.runUiThreadTasks(); //will do onPostExecute

        ShadowMyFilesLog.verifyLog(expectedResultLog);
        ShadowMyFilesLog.verifyLog("_clearCache all");
    }


    @Test
    public void testRenameOperation() {
        ShadowMyFilesLog.setEnableVerifyLog(true);
        ShadowSamsungAnalyticsLog.setEnableVerifyLog(true);

        FileOperationArgs args = new FileOperationArgs();
        args.mSrc = new LocalFileRecord(AppConstants.StoragePath.INTERNAL_ROOT + "/renameFile.txt");
        args.mDst = mDstFile;


        mFileOperator.setOperationArgs(FileOperator.Operation.RENAME, args);
        mFileOperator.execute(); //will do onPreExecute
        Assert.assertNotNull(mFileOperator);

        ShadowApplication.runBackgroundTasks(); //will do doInBackground

        Assert.assertNotNull(mFileOperator);
        ShadowLooper.runUiThreadTasks(); //will do onPostExecute

        ShadowMyFilesLog.verifyLog("ret = true");
        ShadowMyFilesLog.verifyLog("_clearCache all");
    }


    @Test
    public void testOperation() {
        testFileOperation(FileOperator.Operation.COPY);
        testFileOperation(FileOperator.Operation.MOVE);
        testFileOperation(FileOperator.Operation.DELETE);
    }

    private void testFileOperation(FileOperator.Operation operationType) {
        ShadowMyFilesLog.setEnableVerifyLog(true);
        ShadowSamsungAnalyticsLog.setEnableVerifyLog(true);

        FileOperationArgs args = new FileOperationArgs();
        args.mDst = mDstFile;
        args.mSrcList = mSrcFileList;

        mFileOperator.setOperationArgs(operationType, args);
        mFileOperator.execute(); //will do onPreExecute
        Assert.assertNotNull(mFileOperator);

        ShadowApplication.runBackgroundTasks(); //will do doInBackground

        Assert.assertNotNull(mFileOperator);
        ShadowLooper.runUiThreadTasks(); //will do onPostExecute

        ShadowMyFilesLog.verifyLog("ret = true");
        ShadowMyFilesLog.verifyLog("_clearCache all");
        verifyFiles(mSrcFileList);
    }

    private void verifyFiles(ArrayList<FileRecord> recordList) {
        for (FileRecord record : recordList) {
            ShadowMyFilesLog.verifyLog(record.getName());
            if (record.isDirectory()) {
                File dir = SemFwWrapper.file(record.getFullPath());
                File[] filesInDir = dir.listFiles();
                Assert.assertNotNull(filesInDir);
                ArrayList<FileRecord> list = new ArrayList<>();
                for (File file : filesInDir) {
                    list.add(new LocalFileRecord(file.getAbsolutePath()));
                }
                verifyFiles(list);
            }
        }
    }

    @Test
    public void testGetSuccessMsg() throws Exception {
        mFileOperator.setOperationArgs(FileOperator.Operation.COMPRESS, null);
        String actual = WhiteboxImpl.invokeMethod(mFileOperator, "getSuccessMsg");
        Assert.assertEquals(mContext.getString(R.string.compressed), actual);


        mFileOperator.setOperationArgs(FileOperator.Operation.COPY, null);
        actual = WhiteboxImpl.invokeMethod(mFileOperator, "getSuccessMsg");
        Assert.assertEquals("", actual);

    }

    @Test
    public void testGetErrorMsg() throws Exception {
        Resources res = mContext.getResources();

        FileOperationArgs args = new FileOperationArgs();


        final String presetMsg = "test";


        errorMsgTest(res, FileOperator.Operation.COPY, args, presetMsg, R.plurals.failed_copy_folder, R.plurals.failed_copy_file, R.string.failed_copy_items);
        errorMsgTest(res, FileOperator.Operation.MOVE, args, presetMsg, R.plurals.failed_move_folder, R.plurals.failed_move_file, R.string.failed_move_items);
        errorMsgTest(res, FileOperator.Operation.DELETE, args, presetMsg, R.plurals.failed_delete_folder, R.plurals.failed_delete_file, R.string.failed_delete_items);

        errorMsgTest(res, FileOperator.Operation.CREATE_DIR, args, presetMsg, -1, -1, R.string.failed_to_create_folder);
        errorMsgTest(res, FileOperator.Operation.RENAME, args, presetMsg, -1, -1, R.string.failed_to_rename);
        errorMsgTest(res, FileOperator.Operation.COMPRESS, args, presetMsg, -1, -1, R.string.error);
        errorMsgTest(res, FileOperator.Operation.DECOMPRESS, args, presetMsg, -1, -1, R.string.error);
        errorMsgTest(res, FileOperator.Operation.PREVIEW_COMPRESS, args, presetMsg, -1, -1, R.string.error);

    }

    private void errorMsgTest(Resources res, FileOperator.Operation operationType, FileOperationArgs args, String errorMsg, int folder, int file, int items) throws Exception {
        String expected;
        mFileOperator.setOperationArgs(operationType, args);

        Whitebox.setInternalState(mFileOperator, "mErrorMsg", errorMsg);
        String ret = WhiteboxImpl.invokeMethod(mFileOperator, "getErrorMsg");
        Assert.assertEquals(errorMsg, ret);

        if (folder >= 0) {
            ArrayList<FileRecord> recordList = createFileRecordList(true, false);
            expected = res.getQuantityString(folder, recordList.size(), recordList.size());
            errorMsgTest(args, expected, recordList);
        }

        if (file >= 0) {
            ArrayList<FileRecord> recordList = createFileRecordList(false, true);
            expected = res.getQuantityString(file, recordList.size(), recordList.size());
            errorMsgTest(args, expected, recordList);
        }

        if (items >= 0) {
            errorMsgTest(args, res.getString(items), createFileRecordList(true, true));
        }
    }

    private void errorMsgTest(FileOperationArgs args, String expected, ArrayList<FileRecord> recordList) throws Exception {
        Whitebox.setInternalState(mFileOperator, "mErrorMsg", (String) null);
        args.mSrcList = recordList;
        String actual = WhiteboxImpl.invokeMethod(mFileOperator, "getErrorMsg");
        Assert.assertEquals(expected, actual);
    }

    private ArrayList<FileRecord> createFileRecordList(boolean hasFolder, boolean hasFile) {
        ArrayList<FileRecord> ret = new ArrayList<>();
        if (hasFolder) {
            String fileList[] = {
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src/dir1",
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src/dir2"
            };
            addPathToRecordList(fileList, ret);
        }

        if (hasFile) {
            String fileList[] = {
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src/test1.txt",
                    AppConstants.StoragePath.INTERNAL_ROOT + "/src/test2.txt"
            };
            addPathToRecordList(fileList, ret);
        }

        return ret;
    }

    private void addPathToRecordList(String fileList[], ArrayList<FileRecord> recordList) {
        for (String file : fileList) {
            recordList.add(new LocalFileRecord(file));
        }
    }

    @Test
    public void testGetDuplicatePolicy() {
        for (FileOperator.DuplicatePolicy policy : FileOperator.DuplicatePolicy.values()) {
            Whitebox.setInternalState(mFileOperator, "mDuplicatePolicyApplyAll", false);
            Whitebox.setInternalState(mFileOperator, "mDuplicatePolicy", policy);
            Assert.assertEquals(policy, mFileOperator.getDuplicatePolicy());
            Assert.assertEquals(FileOperator.DuplicatePolicy.none, mFileOperator.getDuplicatePolicy());

            Whitebox.setInternalState(mFileOperator, "mDuplicatePolicyApplyAll", true);
            Whitebox.setInternalState(mFileOperator, "mDuplicatePolicy", policy);
            Assert.assertEquals(policy, mFileOperator.getDuplicatePolicy());
            Assert.assertEquals(policy, mFileOperator.getDuplicatePolicy());
        }
    }

    @Test
    public void testGetDstFileRecordForDuplicated() throws Exception {
        FileRecord actual;
        FileRecord record = new LocalFileRecord(AppConstants.StoragePath.INTERNAL_ROOT + "/duplicated.txt");


        Whitebox.setInternalState(mFileOperator, "mDuplicatePolicy", FileOperator.DuplicatePolicy.rename);
        actual = WhiteboxImpl.invokeMethod(mFileOperator, "getDstFileRecordForDuplicated", record);
        Assert.assertEquals(AppConstants.StoragePath.INTERNAL_ROOT + "/duplicated (1).txt", actual.getFullPath());


        Whitebox.setInternalState(mFileOperator, "mDuplicatePolicy", FileOperator.DuplicatePolicy.replace);
        actual = WhiteboxImpl.invokeMethod(mFileOperator, "getDstFileRecordForDuplicated", record);
        Assert.assertEquals(record, actual);

        Whitebox.setInternalState(mFileOperator, "mDuplicatePolicy", FileOperator.DuplicatePolicy.cancel);
        actual = WhiteboxImpl.invokeMethod(mFileOperator, "getDstFileRecordForDuplicated", record);
        Assert.assertNull(actual);
    }


    @Test
    public void testCompress() throws Exception {
        CompressOptions option = new CompressOptions();
        option.mCompressMode = CompressOptions.CompressMode.COMPRESS;
        option.mCompressType = CompressOptions.CompressType.ZIP;
        option.mSrcRoot = mSrcFileList.get(0).getParent();

        FileOperationArgs args = new FileOperationArgs();
        args.mProcessId = testFeature.PROCESS_ID;
        mFileOperator.setOperationArgs(FileOperator.Operation.COMPRESS, args);

        mDstFile = new LocalFileRecord(AppConstants.StoragePath.INTERNAL_ROOT + "/dst_dir/dst.zip");

        boolean bRet = WhiteboxImpl.invokeMethod(mFileOperator, "compress", mSrcFileList, mDstFile, option);
        Assert.assertTrue(bRet);
    }

}
